#pragma once

#include <set>
#include <string>
#include <vector>

#include "../io/DbInterface.h"
#include "../evaluator/EstimateParasitics.h"
#include "../evaluator/RoutingTree.h"

// #include "BufferedOption.h"
// #include "ViolationOptimizer.h"

// #include "ids.hpp"

namespace ieda_contest {


class TimingOptimizer {
 public:
  TimingOptimizer(DbInterface *dbinterface);

  ~TimingOptimizer() {
    delete _parasitics_estimator;
    // delete _violation_fixer;
  }
  TimingOptimizer(const TimingOptimizer &other) = delete;
  TimingOptimizer(TimingOptimizer &&other) = delete;

  // open functions
  void doGateSizing(double time, uint64_t &init_area, uint64_t &max_area);

  void doDownSize(uint64_t &init_area, uint64_t &max_area);

 private:
  void initBufferCell();

  void doGateSizing(StaSeqPathData *worst_path, Slack path_slack);
  void doGateSizing(StaVertex *vertex, Slack path_slack);

  VertexSeq findViolationNode();
  void downSize(StaVertex *vertex);

  LibertyCell *upsizeCell(LibertyPort *in_port, LibertyPort *drvr_port, float load_cap,
                          float prev_drive);

  // BufferedOptionSeq bottomUpBuffering(RoutingTree *tree, int curr_id, int prev_id,
  //                                     int level);

  // BufferedOptionSeq mergeBranch(BufferedOptionSeq buf_opt_left,
  //                               BufferedOptionSeq buf_opt_right, Point curr_loc);

  // BufferedOptionSeq addWireAndBuffer(BufferedOptionSeq buf_opt_seq,
  //                                    //  RoutingTree *tree,
  //                                    Point curr_loc, Point prev_loc, int level);

  // BufferedOptionSeq addBuffer(BufferedOptionSeq buf_opt_seq, Point prev_loc);

  // void topDownImplementBuffering(BufferedOption *buf_opt, Net *net, int level);

  // float calcBufferDelay(LibertyCell *buffer_cell, float load_cap);
  // float calcBufferDelay(LibertyCell *buffer_cell, float load_cap, TransType rf);

  float calcGateDelay(LibertyPort *drvr_port, float load_cap, TransType rf);

  float calcGateDelay(LibertyPort *drvr_port, float load_cap);

  int getFanoutNumber(Pin *pin);

  bool hasTopLevelOutputPort(Net *net);

  void setLocation(Instance *inst, int x, int y);

  StaSeqPathData *worstRequiredPath();

  bool netConnectToPort(Net *net);

  Slack getWorstSlack(StaVertex *vertex, AnalysisMode mode);
  VertexSet getEndPoints();
  void      findEndpointsWithSetupViolation(VertexSet end_points, Slack slack_margin,
                                            // return values
                                            VertexSeq &setup_violations);

  bool repowerInstance(Pin *drvr_pin);
  bool repowerInstance(Instance *inst, LibertyCell *replace);

  double calcBufferDelay(LibertyCell *buffer_cell, float load) {
    LibertyPort *input, *output;
    buffer_cell->bufferPorts(input, output);
    Delay gate_delays[2];
    Slew  slews[2];
    calcGateRiseFallDelays(output, load, gate_delays, slews);
    return std::max(gate_delays[_rise], gate_delays[_fall]);
  }
  void calcGateRiseFallDelays(LibertyPort *drvr_port, float load_cap,
                                                // return values.
                                                Delay delays[], Slew slews[]) {
    for (int rf_index = 0; rf_index < 2; rf_index++) {
      delays[rf_index] = -kInf;
      slews[rf_index] = -kInf;
    }

    LibertyCell *cell = drvr_port->get_ower_cell();
    // get all cell arcset
    auto &cell_arcset = cell->get_cell_arcs();
    for (auto &arcset : cell_arcset) {
      ieda::Vector<std::unique_ptr<ista::LibertyArc>> &arcs = arcset->get_arcs();
      for (auto &arc : arcs) {
        if (arc->isDelayArc()) {
          if ((arc->get_timing_type() == LibertyArc::TimingType::kComb) ||
              (arc->get_timing_type() == LibertyArc::TimingType::kCombRise) ||
              (arc->get_timing_type() == LibertyArc::TimingType::kRisingEdge) ||
              (arc->get_timing_type() == LibertyArc::TimingType::kDefault)) {
            gateRiseFallDelay(TransType::kRise, arc.get(), load_cap, delays, slews);
          }

          if ((arc->get_timing_type() == LibertyArc::TimingType::kComb) ||
              (arc->get_timing_type() == LibertyArc::TimingType::kCombFall) ||
              (arc->get_timing_type() == LibertyArc::TimingType::kFallingEdge) ||
              (arc->get_timing_type() == LibertyArc::TimingType::kDefault)) {
            gateRiseFallDelay(TransType::kFall, arc.get(), load_cap, delays, slews);
          }
        }
      }
    }
  }

  void gateRiseFallDelay(TransType rf, LibertyArc *arc, float load_cap,
                                            // return values.
                                            Delay delays[], Slew slews[]) {
    int   rise_fall = (int)rf - 1;
    float in_slew = _db_interface->get_target_slews()[rise_fall];
    Delay gate_delay;
    Slew  drvr_slew;
    gate_delay = arc->getDelayOrConstrainCheckNs(rf, in_slew, load_cap);
    drvr_slew = arc->getSlewNs(rf, in_slew, load_cap);
    delays[rise_fall] = std::max(delays[rise_fall], gate_delay);
    slews[rise_fall] = std::max(slews[rise_fall], drvr_slew);
  }


  // data
  DbInterface     *_db_interface;
  TimingEngine    *_timing_engine;
  TimingDBAdapter *_db_adapter;

  EstimateParasitics *_parasitics_estimator;
  // ViolationOptimizer *_violation_fixer;

  int _resize_instance_count;
  int _inserted_buffer_count;

  // to name the instance
  int _insert_instance_index;
  // to name the net
  int _make_net_index;

  int _dbu;

  static int _rise;
  static int _fall;

  LibertyCellSeq _buf_cells;

  bool _exit_program = false;
  double _total_time = 0;

  uint64_t _init_instance_area;
  uint64_t _max_instance_area;


};

} // namespace ieda_contest